<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="Rrydy" id="Rrydy"><span data-lake-id="uf87b3169" id="uf87b3169">典型回答</span></h1>
  <p data-lake-id="u90ed4af2" id="u90ed4af2"><span data-lake-id="uc060428f" id="uc060428f">在我们通过IDEA编写Spring的代码的时候，假如我们编写了如下代码：</span></p>
  <p data-lake-id="u940bc759" id="u940bc759"><span data-lake-id="u6c8d93cb" id="u6c8d93cb">​</span><br></p>
  <pre lang="java"><code>
@Autowired
private Bean bean;
</code></pre>
  <p data-lake-id="ua7eb0464" id="ua7eb0464"><span data-lake-id="u59b600ee" id="u59b600ee">IDEA会给我们一个warning警告：</span></p>
  <p data-lake-id="u3ec7851f" id="u3ec7851f"><span data-lake-id="ubc89f9e4" id="ubc89f9e4">​</span><br></p>
  <blockquote data-lake-id="uec153b7d" id="uec153b7d">
   <p data-lake-id="uab40601d" id="uab40601d"><span data-lake-id="u49a72edd" id="u49a72edd">Field injection is not recommended</span></p>
  </blockquote>
  <p data-lake-id="ue679bd51" id="ue679bd51"><br></p>
  <p data-lake-id="u424d8f41" id="u424d8f41"><span data-lake-id="ue974226f" id="ue974226f">翻阅</span><a href="https://docs.spring.io/spring-framework/docs/5.1.3.RELEASE/spring-framework-reference/core.html#beans-dependencies" target="_blank" data-lake-id="u91eca022" id="u91eca022"><span data-lake-id="u92fa8dfc" id="u92fa8dfc">官方文档</span></a><span data-lake-id="uc53de313" id="uc53de313">我们会发现：</span></p>
  <blockquote data-lake-id="u4b29596b" id="u4b29596b">
   <p data-lake-id="ubb2b2159" id="ubb2b2159"><span data-lake-id="ue30f57f8" id="ue30f57f8">Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies. </span></p>
  </blockquote>
  <p data-lake-id="u417d55b0" id="u417d55b0"><br></p>
  <p data-lake-id="u8854ea51" id="u8854ea51"><span data-lake-id="ud8288d1f" id="ud8288d1f">大意就是</span><strong><span data-lake-id="u767939c5" id="u767939c5">强制依赖使用构造器注入，可选依赖使用setter注入。</span></strong><span data-lake-id="u17cfe702" id="u17cfe702">那么这是为什么呢？使用字段注入又会导致什么问题呢？</span></p>
  <h2 data-lake-id="nOhaL" id="nOhaL"><span data-lake-id="ufc4fa0c1" id="ufc4fa0c1">单一职责问题</span></h2>
  <p data-lake-id="ud252648a" id="ud252648a"><span data-lake-id="u1e5b7f7f" id="u1e5b7f7f">我们都知道，根据SOLID设计原则来讲，一个类的设计应该符合单一职责原则，就是一个类只能做一件功能，当我们使用基于字段注入的时候，随着业务的暴增，字段越来越多，我们是很难发现我们已经默默中违背了单一职责原则的。</span></p>
  <p data-lake-id="u874f175e" id="u874f175e"><span data-lake-id="ua3b8f0b8" id="ua3b8f0b8">​</span><br></p>
  <p data-lake-id="u12fd633e" id="u12fd633e"><span data-lake-id="u5bf6c7c2" id="u5bf6c7c2">但是如果我们使用基于构造器注入的方式，因为构造器注入的写法比较臃肿，所以它就在间接提醒我们，违背了单一职责原则，该做重构了</span></p>
  <pre lang="java"><code>
@Component
class Test {
    @Autowired
    private Bean bean1;

    @Autowired
    private Bean bean2;

    @Autowired
    private Bean bean3;

    @Autowired
    private Bean bean4;
}
</code></pre>
  <pre lang="java"><code>
@Component
class Test {

    private final Bean bean1;

    private final Bean bean2;

    private final Bean bean3;

    private final Bean bean4;
    
	@Autowired
    public Test(Bean bean1, Bean bean2, 
               Bean bean3, Bean bean4) {
        this.bean1 = bean1;
        this.bean2 = bean1;
 		this.bean3 = bean1;
     	this.bean4 = bean1;
    }
}
</code></pre>
  <h2 data-lake-id="zIuSc" id="zIuSc"><span data-lake-id="uaa9ace12" id="uaa9ace12">可能产生NPE</span></h2>
  <p data-lake-id="u62b33f8d" id="u62b33f8d"><span data-lake-id="u66d0788c" id="u66d0788c">对于一个bean来说，它的初始化顺序为：</span></p>
  <p data-lake-id="uf77e0eb9" id="uf77e0eb9"><span data-lake-id="u16f25add" id="u16f25add">​</span><br></p>
  <p data-lake-id="u16dd17fd" id="u16dd17fd"><span data-lake-id="u3a8888ef" id="u3a8888ef">静态变量或静态语句块 -&gt; 实例变量或初始化语句块 -&gt; 构造方法 -&gt; @Autowired</span></p>
  <p data-lake-id="uffcb200d" id="uffcb200d"><span data-lake-id="u07ad9cc7" id="u07ad9cc7">​</span><br></p>
  <p data-lake-id="uf902776d" id="uf902776d"><span data-lake-id="u24a1af00" id="u24a1af00">所以，在静态语句块，初始化语句块，构造方法中使用Autowired表明的字段，都会引起NPE问题</span></p>
  <pre lang="java"><code>
@Component
class Test {
    @Autowired
    private Bean bean;

    private final String beanName;

    public Test() {
        // 此时bean尚未被初始化，会抛出NPE
        this.beanName = bean.getName();
    }
}
</code></pre>
  <p data-lake-id="uf6db03c0" id="uf6db03c0"><span data-lake-id="u047cca2d" id="u047cca2d">​</span><br></p>
  <p data-lake-id="u65bc8a92" id="u65bc8a92"><span data-lake-id="u6a8df926" id="u6a8df926">相反，用构造器的DI，就会实例化对象在使用的过程中，字段一定不为空。</span></p>
  <h2 data-lake-id="m2ol2" id="m2ol2"><span data-lake-id="ucb04b5e5" id="ucb04b5e5">隐藏依赖</span></h2>
  <p data-lake-id="uce4bed3f" id="uce4bed3f"><span data-lake-id="uffb007a7" id="uffb007a7">对于一个正常的使用依赖注入的Bean来说，它应该“显式”的通知容器，自己需要哪些Bean，可以通过构造器通知，public的setter方法通知，这些设计都是没问题的。</span></p>
  <p data-lake-id="u191f6d47" id="u191f6d47"><span data-lake-id="ua35b4a2b" id="ua35b4a2b">​</span><br></p>
  <p data-lake-id="u103bd14f" id="u103bd14f"><span data-lake-id="u998fbe1f" id="u998fbe1f">外部容器不应该感知到Bean内部私有字段(如上例中的private bean)的存在，私有字段对外部应该是不可见的。由于私有字段不可见，所以在设计层面，我们不应该通过字段注入的方式将依赖注入到私有字段中。这样会破坏封装性。</span></p>
  <p data-lake-id="ue8e71b6c" id="ue8e71b6c"><span data-lake-id="u26ab602b" id="u26ab602b">​</span><br></p>
  <p data-lake-id="u4494394a" id="u4494394a"><span data-lake-id="u86304cf6" id="u86304cf6">所以，当我们对字段做注入的时候，Spring就需要关心一个本来被我们封装到一个bean中的私有成员变量，这就和他的封装性违背了。因为我们应该通过setter或者构造函数来修改一个字段的值。</span></p>
  <h2 data-lake-id="I3I0r" id="I3I0r"><span data-lake-id="udf527a64" id="udf527a64">不利于测试</span></h2>
  <p data-lake-id="u2da91852" id="u2da91852"><span data-lake-id="u49f8fdaf" id="u49f8fdaf">很明显，使用了Autowired注解，说明这个类依赖了Spring容器，这让我们在进行UT的时候必须要启动一个Spring容器才可以测试这个类，显然太麻烦，这种测试方式非常重，对于大型项目来说，往往启动一个容器就要好几分钟，这样非常耽误时间。</span></p>
  <p data-lake-id="u62961bfd" id="u62961bfd"><span data-lake-id="u778fb0c5" id="u778fb0c5">​</span><br></p>
  <p data-lake-id="u89a2f3ad" id="u89a2f3ad"><span data-lake-id="u551c443d" id="u551c443d">不过，如果使用构造器的依赖注入就不会有这种问题，或者，我们可以使用Resource注解也可以解决上述问题</span></p>
  <h1 data-lake-id="vVhm5" id="vVhm5"><span data-lake-id="u8bbcf136" id="u8bbcf136">知识扩展</span></h1>
  <h2 data-lake-id="dh4cv" id="dh4cv"><span data-lake-id="ud09cc0a4" id="ud09cc0a4">Spring支持哪些注入方式</span></h2>
  <h3 data-lake-id="VNUT9" id="VNUT9"><span data-lake-id="u407b3cd8" id="u407b3cd8">1. 字段注入</span></h3>
  <pre lang="java"><code>
@Autowired
private Bean bean;
</code></pre>
  <h3 data-lake-id="eFhmw" id="eFhmw"><span data-lake-id="uea73ae1c" id="uea73ae1c">2. 构造器注入</span></h3>
  <pre lang="java"><code>
@Component
class Test {
    private final Bean bean;

    @Autowired
    public Test(Bean bean) {
        this.bean = bean;
    }
}
</code></pre>
  <h3 data-lake-id="ho9Yi" id="ho9Yi"><span data-lake-id="u7ad841de" id="u7ad841de">3. setter注入</span></h3>
  <pre lang="java"><code>
@Component
class Test {
    private Bean bean;

    @Autowired
    public void setBean(Bean bean) {
        this.bean = bean;
    }
}
</code></pre>
  <h2 data-lake-id="WsFDb" id="WsFDb"><span data-lake-id="udf39f9d7" id="udf39f9d7">使用构造器注入可能有哪些问题</span></h2>
  <p data-lake-id="u9eb35243" id="u9eb35243"><span data-lake-id="ua5086e23" id="ua5086e23">如果我们两个bean循环依赖的话，构造器注入就会抛出异常：</span></p>
  <p data-lake-id="u99865690" id="u99865690"><span data-lake-id="u1865249f" id="u1865249f">​</span><br></p>
  <pre lang="java"><code>
@Component
public class BeanTwo implements Bean{

    Bean beanOne;

    public BeanTwo(Bean beanOne) {
        this.beanOne = beanOne;
    }
}
@Component
public class BeanOne implements Bean{

    Bean beanTwo;

    public BeanOne(Bean beanTwo) {
        this.beanTwo = beanTwo;
    }
}
</code></pre>
  <blockquote data-lake-id="u3456b7cd" id="u3456b7cd">
   <p data-lake-id="u07d2a613" id="u07d2a613"><span data-lake-id="uace270c0" id="uace270c0">Error creating bean with name 'beanOne': Requested bean is currently in creation: Is there an unresolvable circular reference?</span></p>
  </blockquote>
  <p data-lake-id="ua941fbd1" id="ua941fbd1"><br></p>
  <p data-lake-id="u662144d0" id="u662144d0"><span data-lake-id="u71b0bdb3" id="u71b0bdb3">如果两个类彼此循环引用，那说明代码的设计一定是有问题的。如果临时解决不了，我们可以在某一个构造器中加入@Lazy注解，让一个类延迟初始化即可。</span></p>
  <p data-lake-id="uf02618d0" id="uf02618d0"><span data-lake-id="uc200c8ed" id="uc200c8ed">​</span><br></p>
  <pre lang="java"><code>
@Component
public class BeanOne implements Bean{

    Bean beanTwo;

    @Lazy
    public BeanOne(Bean beanTwo) {
        this.beanTwo = beanTwo;
    }
}
</code></pre>
 </body>
</html>